package com.jplus.core.util;

import java.util.HashMap;
import java.util.Stack;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

/**
 * <b>Jplus框架表达式（JPEL）</b></br> 使用逆波兰算法，支持：
 * <ul>
 * <li><b>算数运算表达式:</b><br/>
 * 加(+)、减(-)、乘(*)、除(/)、求余（%）、幂（^）运算</li>
 * <li><b>关系表达式:</b><br/>
 * 等于（==）、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=)</li>
 * <li><b>逻辑表达式:</b><br/>
 * 且（&&）、或( || )、非( ! )、true、false</li>
 * <li><b>括号优先级表达式:</b><br/>
 * 使用“(表达式)”构造，括号里的具有高优先级。</li>
 * </ul>
 * 
 * 不支持三目运算，不支持文本字符串
 * 
 * @author Yuanqy
 *
 */
public class JPEL {
	private final static HashMap<String, Integer> priority = new HashMap<>();

	private static int MIN = 100;
	private static String EOD = "#";

	private static int getPriority(String key) {
		if (priority.get(key) == null)
			return MIN;
		return priority.get(key);
	}

	/**
	 * 执行表达式
	 * 
	 * @param calc
	 * @return
	 */
	public static Object excute(String calc) {
		Stack<String> s = createReversePolish(calc);
		return calcReversePolish(s);
	}

	static {
		// 加入优先级映射,可继续扩展，级别按百科填写
		// priority.put("?:", 13);
		priority.put("||", 12);
		priority.put("&&", 11);

		priority.put("!=", 7);
		priority.put("==", 7);

		priority.put("<=", 6);
		priority.put(">=", 6);
		priority.put("<", 6);
		priority.put(">", 6);

		priority.put("+", 4);
		priority.put("-", 4);

		priority.put("*", 3);
		priority.put("/", 3);
		priority.put("%", 3);

		priority.put("!", 2);
		priority.put("^", 2);// 幂运算,js引擎是不支持这么写的

		priority.put("(", 1);
		priority.put(")", 1);
	}

	// == 解析 ========================================================
	public static Stack<String> createReversePolish(String source) {
		String[] str = editExpress(source);
		Stack<String> s1 = new Stack<String>();// 运算符
		Stack<String> s2 = new Stack<String>();// 逆波兰
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < str.length; i++) {
			String t = str[i];
			if (t.equals(""))
				continue;
			if (t.equals(EOD)) {// 结束符
				while (true) {
					if (s1.size() == 0)
						break;
					s2.push(s1.pop());
				}
				break;
			}
			sb.append(t);
			if (isNumber(t)) {
				// 若取出的字符是操作数，则分析出完整的运算数，该操作数直接送入S2栈
				if (!isNumber(str[i + 1]))
					RefreshTemp(s2, sb);
			} else {
				// 若取出的字符是“（”，则直接送入S1栈顶。
				// 若取出的字符是“）”，则将距离S1栈栈顶最近的“（”之间的运算符，逐个出栈，依次送入S2栈，此时抛弃“（”。
				if (getPriority(t) == 1) {
					if (t.equals("(")) {
						spush(s1, s2, sb.deleteCharAt(sb.length() - 1), sb.toString());
						s1.push(t);
					}
					if (t.equals(")")) {
						while (true) {
							String s1t = s1.pop();
							if (s1t.equals("("))
								break;
							s2.push(s1t);
						}
					}
					sb.setLength(0);// ?
				} else if (isNumber(str[i + 1])) {
					// 若取出的字符是运算符，则将该运算符与S1栈栈顶元素比较，如果该运算符优先级大于S1栈栈顶运算符优先级，则将该运算符进S1栈，
					// 否则，将S1栈的栈顶运算符弹出，送入S2栈中，直至S1栈栈顶运算符低于（不包括等于）该运算符优先级，最后将该运算符送入S1栈。
					spush(s1, s2, sb, t);
				}
			}
		}
		s1.clear();
		return s2;
	}

	private static String[] editExpress(String source) {
		source = "(" + source.replaceAll("\\s*", "") + ")" + EOD;// 去除空格
		source = source.replaceAll("true", "1");// 支持true/false
		source = source.replaceAll("false", "0");
		source = source.replaceAll("([^\\d|^)])([+|-])(\\d+)", "$1(0$2$3)");// 支持一元[+-]
		return source.split("");
	}

	private static void spush(Stack<String> s1, Stack<String> s2, StringBuilder sb, String t) {
		while (true) {
			if (s1.size() == 0 || getPriority(t) < getPriority(s1.peek()) || getPriority(s1.peek()) == 1) {
				RefreshTemp(s1, sb);
				break;
			} else {
				s2.push(s1.pop());
			}
		}
	}

	private static void RefreshTemp(Stack<String> rpStack, StringBuilder sb) {
		String str = sb.toString();
		if (str.length() > 0) {
			if ((isNumber(str) || getPriority(str) < MIN)) {
				rpStack.push(sb.toString());
				sb.setLength(0);
			} else {
				throw new ArithmeticException("错误的表达式：" + str);
			}
		}
	}

	private static boolean isNumber(String t) {
		char i = t.charAt(0);// 值判断一个字符
		return (i >= 48 && i <= 57) ? true : false;
	}

	// == 计算 ========================================================
	public static Object calcReversePolish(Stack<String> s2) {
		Stack<Object> no = new Stack<Object>();
		for (String s : s2) {
			if (isNumber(s)) {
				no.push(Double.parseDouble(s));
			} else {
				Object d2 = no.pop();
				Object d1 = no.pop();
				switch (s) {
				case "!":
					no.push(!((double) d2 == 1));
					break;
				case "+":
					no.push((double) d1 + (double) d2);
					break;
				case "-":
					no.push((double) d1 - (double) d2);
					break;
				case "*":
					no.push((double) d1 * (double) d2);
					break;
				case "/":
					no.push((double) d1 / (double) d2);
					break;
				case "%":
					no.push((double) d1 % (double) d2);
					break;
				case "==":
					no.push((double) d1 == (double) d2 ? true : false);
					break;
				case "!=":
					no.push((double) d1 != (double) d2 ? true : false);
					break;
				case ">=":
					no.push(((double) d1 >= (double) d2 ? true : false));
					break;
				case "<=":
					no.push(((double) d1 <= (double) d2 ? true : false));
					break;
				case ">":
					no.push(((double) d1 > (double) d2 ? true : false));
					break;
				case "<":
					no.push(((double) d1 < (double) d2 ? true : false));
					break;
				case "||":
					no.push(((boolean) d1 || (boolean) d2 ? true : false));
					break;
				case "&&":
					no.push(((boolean) d1 && (boolean) d2 ? true : false));
					break;
				case "^":
					no.push(Math.pow((double) d1, (double) d2));
					break;
				}
			}
		}
		return no.pop();
	}

	// == 附加 js 引擎
	/**
	 * Must java version >=1.6
	 */
	private static ScriptEngine se;

	public static ScriptEngine getSE() {
		if (se == null)
			se = new ScriptEngineManager().getEngineByName("js");// 基本每次验证会多消耗5毫秒时间
		return se;
	}

	public static void main(String[] args) throws ScriptException {
		// String calc = "+11+-22-33 ";
		// String calc="10%4*3";
		String calc = "1+2*(3+4)-5+(-2)*8/2+8 == 5*6/2-5 && 10%4*3>-1+2*3";
		// String calc = "!false";
		System.err.println("中缀表达式：" + calc);
		// == 逆波兰 ========================
		long t = ClockUtil.CLOCK.now();
		System.out.println("逆波兰=" + excute(calc) + "\ttime:" + (ClockUtil.CLOCK.now() - t));
		// == JS引擎==========================
		t = ClockUtil.CLOCK.now();
		System.out.println("JS引擎=" + getSE().eval(calc) + "\ttime:" + (ClockUtil.CLOCK.now() - t));
	}
}
